//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------
/*
	Widget - base class for all UI components.
*/

#pragma once

#include "UI/Types/ColorStyle.h"
#include "UI/Types/WidgetMsg.h"
#include "UI/Types/WidgetLayout.h"
#include "UI/Types/UIMaterial.h"
#include "UI/Types/UIFont.h"
#include "UI/Types/UIImage.h"
#include "UI/Systems/Screen.h"

namespace Engine
{
namespace UI
{

	struct EWidget
	{
		enum type
		{
			UNKNOWN	= 0,
			WIDGET,
			LABEL,
			BUTTON,
			TEXTBOX,
			PICTURE,
			GROUP,
			GROUP_SWITCH,
			RADIO_BUTTON,
			PROGRESS_BAR,
			PANEL,
			FRAME,

			CUSTOM = 0x100
		};
	};



	//
	// UI Widget
	//

	class Widget : public BaseObject
	{
		friend struct ::MetaData::WidgetCfg;

	// types
	public:
		typedef array< WidgetPtr >					widget_array_t;
		typedef ArrangeHelper::layout_array_t		layout_array_t;


	// variables
	protected:
		WidgetPtr			_parent;
		widget_array_t		_attachedWidgets;
		string				_name;
		layout_array_t		_layouts;
		rect				_region;		// compute from layout
		rect				_dockRegion;	// used for dock arrange
		ELayout::bits		_layoutMask;
		const EWidget::type	_widgetType;
		bool				_visible;
		bool				_visibleForLayout;
		bool				_enabled;
		bool				_stateChanged;	// true if changed any state that has affect to visualisation of widget


	// methods
	public:
		Widget (EWidget::type wgtType, const EngineSubSystemsRef ess);
		virtual ~Widget ();

		rect &					GetDockRegion ()		{ return _dockRegion; }
		rect const &			GetRegion ()	 const	{ return _region; }
		WidgetPtr const &		GetParent ()	 const	{ return _parent; }
		EWidget::type			GetType ()		 const	{ return _widgetType; }
		widget_array_t const &	GetWidgets ()	 const	{ return _attachedWidgets; }
		layout_array_t const &	GetLayouts ()	 const	{ return _layouts; }
		layout_array_t		 &	GetLayouts ()			{ return _layouts; }
		string const &			GetName ()		 const	{ return _name; }

		bool IsVisible ()	const;
		bool IsEnabled ()	const			{ return _enabled; }

		void SetVisible (bool visible, bool recursive = false);
		void SetEnabled (bool enabled, bool recursive = false);
		
		bool FindWidget (uni_c_string name, WidgetPtr &widget);

		static WidgetPtr New (const EngineSubSystemsRef ess);


	// interface
	public:
		virtual bool ProcessMessage (const WidgetMsg &);


	// hidden methods
	protected:
		bool _Update (const WidgetMsg &msg);
		bool _Draw (const WidgetMsg &msg);

		bool _GetWidget (const WidgetMsg &msg);

		bool _Attach (const WidgetMsg::Attach &msg);
		bool _Detach (const WidgetMsg::Attach &msg);

		bool _Attached (const WidgetMsg::Attach &msg);
		bool _Detached (const WidgetMsg::Attach &msg);
		
		void _Destroy ();

		bool _Arrange (const WidgetMsg &msg);
		bool _OnLoad (const WidgetMsg &msg);
		bool _SetState (const WidgetMsg &msg);

		bool _ProcessAndTransmit (const WidgetMsg &msg);

		bool _ComputeInnerSize (INOUT WidgetLayout &layout, const WidgetMsg &msg);

		void _CheckChangedOnUpdate (const WidgetMsg &msg);

		// helpers
		void _SendToAllAttached (const WidgetMsg &msg);
		bool _SendToAttachedWhile (const WidgetMsg &msg, bool returnIfResult = true);
		void _SendToAllAttachedRecursive (const WidgetMsg &msg);
		void _SendToParent (const WidgetMsg &msg);
	};

	
	
/*
=================================================
	constructor
=================================================
*/
	inline Widget::Widget (EWidget::type wgtType, const EngineSubSystemsRef ess) :
		BaseObject(ess), _visible(false), _enabled(false), _widgetType(wgtType),
		_layoutMask(ELayout::ALL_BITS), _visibleForLayout(false), _stateChanged(false)
	{
	}
		
/*
=================================================
	destructor
=================================================
*/
	inline Widget::~Widget ()
	{
		ASSERT( _attachedWidgets.Empty() );
	}
	
/*
=================================================
	ProcessMessage
=================================================
*/
	inline bool Widget::ProcessMessage (const WidgetMsg &msg)
	{
		typedef WidgetMsg	WM;

		switch ( msg.GetType() )
		{
			case WM::UPDATE :				return _Update( msg );
			case WM::DRAW :					return _Draw( msg );
			case WM::ATTACH :				return _Attach( msg.To< WM::Attach >() );
			case WM::DETACH :				return _Detach( msg.To< WM::Attach >() );
			case WM::ATTACHED :				return _Attached( msg.To< WM::Attach >() );
			case WM::DETACHED :				return _Detached( msg.To< WM::Attach >() );
			case WM::ARRANGE :				return _Arrange( msg );
			case WM::ON_LOAD :				return _OnLoad( msg );
			case WM::GET_WIDGET :			return _GetWidget( msg );
			case WM::PROCESS_AND_TRANSMIT :	return _ProcessAndTransmit( msg );
			case WM::SET_STATE :			return _SetState( msg );
		}

		return false;
	}
	
/*
=================================================
	_Update
=================================================
*/
	inline bool Widget::_Update (const WidgetMsg &msg)
	{
		if ( not IsVisible() )
			return true;

		_CheckChangedOnUpdate( msg );

		_SendToAllAttached( msg );
		return true;
	}
	
/*
=================================================
	_Draw
=================================================
*/
	inline bool Widget::_Draw (const WidgetMsg &msg)
	{
		if ( not IsVisible() )
			return true;

		_SendToAllAttached( msg );
		return true;
	}

/*
=================================================
	_Attach
=================================================
*/
	inline bool Widget::_Attach (const WidgetMsg::Attach &msg)
	{
		CHECK_ERR( msg.widget != null );

		if ( msg.widget->_parent == this )
		{
			WARNING( "widget was already attached!" );
			return true;
		}

		// detach
		if ( msg.widget->_parent != null )
		{
			msg.widget->_parent->ProcessMessage( WidgetMsg( WidgetMsg::DETACH, WidgetMsg::Attach( msg.widget ) ) );
			
			ASSERT( msg.widget->_parent == null );
		}

		// attach
		_attachedWidgets.PushBack( msg.widget );

		// send on attached message
		msg.widget->ProcessMessage( WidgetMsg( WidgetMsg::ATTACHED,	WidgetMsg::Attach( msg.widget, this ) ) );
		
		ASSERT( msg.widget->_parent == this );

		_stateChanged = true;
		return true;
	}
	
/*
=================================================
	_Attached
=================================================
*/
	inline bool Widget::_Attached (const WidgetMsg::Attach &msg)
	{
		CHECK_ERR( msg.widget == this );

		_parent = msg.parent;
		return true;
	}
	
/*
=================================================
	_Detach
=================================================
*/
	inline bool Widget::_Detach (const WidgetMsg::Attach &msg)
	{
		CHECK_ERR( msg.widget != null );
		CHECK_ERR( msg.widget->_parent == this );

		if ( _attachedWidgets.FindAndErase( WidgetPtr(msg.widget) ) )
		{
			msg.widget->ProcessMessage( WidgetMsg( WidgetMsg::DETACHED, WidgetMsg::Attach( msg.widget, this ) ) );

			msg.widget->_parent = null;
			_stateChanged = true;
			return true;
		}
		return false;
	}
	
/*
=================================================
	_Detached
=================================================
*/
	inline bool Widget::_Detached (const WidgetMsg::Attach &msg)
	{
		CHECK_ERR( msg.widget == this );

		_parent = null;
		return true;
	}
	
/*
=================================================
	_Destroy
=================================================
*/
	inline void Widget::_Destroy ()
	{
		// destroy attached widgets without detaching

		FOR( i, _attachedWidgets )
		{
			_attachedWidgets[i]->_Destroy();
		}

		_attachedWidgets.Clear();
	}
	
/*
=================================================
	_GetWidget
=================================================
*/
	inline bool Widget::_GetWidget (const WidgetMsg &msg)
	{
		typedef WidgetMsg	WM;

		WM::GetWidget const &	gw_msg = msg.To< WM::GetWidget >();

		if ( *gw_msg.name == _name )
		{
			*gw_msg.result = this;
			return true;
		}

		return _SendToAttachedWhile( msg, true );
	}

/*
=================================================
	_OnLoad
=================================================
*/
	inline bool Widget::_OnLoad (const WidgetMsg &msg)
	{
		typedef WidgetMsg	WM;

		widget_array_t	tmp;

		tmp.SwapMem( _attachedWidgets );

		FOR( i, tmp )
		{
			if ( tmp[i]->_parent == null )
			{
				ProcessMessage( WM( WM::ATTACH, WM::Attach( tmp[i].ptr(), null ) ) );
			}
			else
			{
				_attachedWidgets.PushBack( tmp[i] );
			}

			_attachedWidgets.Back()->ProcessMessage( msg );
		}
		return true;
	}
	
/*
=================================================
	_SetState
=================================================
*/
	inline bool Widget::_SetState (const WidgetMsg &msg)
	{
		typedef WidgetMsg	WM;

		WM::SetState const &	st = msg.To< WM::SetState >();

		switch ( st.state )
		{
			case WM::SetState::ENABLED :	_enabled = st.value;	break;
			case WM::SetState::VISIBLE :	_visible = st.value;	break;
		}

		if ( st.recursive ) {
			_SendToAllAttached( msg );
		}

		_stateChanged = true;
		return true;
	}

/*
=================================================
	_ProcessAndTransmit
=================================================
*/
	inline bool Widget::_ProcessAndTransmit (const WidgetMsg &msg)
	{
		typedef WidgetMsg	WM;

		ProcessMessage( *msg.To< WM::WidgetMessage >().msg );

		_SendToAllAttached( msg );
		return true;
	}

/*
=================================================
	_Arrange
=================================================
*/
	inline bool Widget::_Arrange (const WidgetMsg &msg)
	{
		typedef WidgetMsg	WM;

		ArrangeHelper const &	helper = *msg.To< WM::Arrange >().helper;
		
		_visibleForLayout = helper.IsVisibleForLayout( _layoutMask );

		if ( not _visibleForLayout )
			return true;

		helper.Arrange( _region, helper.GetLayout( _layouts ), _parent->GetRegion(), _parent->GetDockRegion() );
		
		_dockRegion = _region;
		_stateChanged = true;

		_SendToAllAttached( msg );
		return true;
	}
	
/*
=================================================
	_ComputeInnerSize
=================================================
*/
	inline bool Widget::_ComputeInnerSize (INOUT WidgetLayout &layout, const WidgetMsg &msg)
	{
		typedef WidgetMsg		WM;

		ArrangeHelper const &	helper	= *msg.To< WM::Arrange >().helper;

		_dockRegion = _region = GetParent()->GetRegion();

		_SendToAllAttached( msg );

		vec2 const	src_size	= _region.GetSize();
		vec2		dst_size	= _dockRegion.GetSize();
		bvec2 const	eq			= src_size == dst_size;

		if ( All( eq ) )		dst_size = vec2( 1.0e-6f );				else
		if ( All( not eq ) )	dst_size = src_size;					else
		if ( not eq.y )			dst_size.y = src_size.y - dst_size.y;	else
		/* not eq.x */			dst_size.x = src_size.x - dst_size.x;
		
		if ( IsZero( layout.size ) )
			helper.UnTransformSize( layout.size, layout.measure, dst_size );

		if ( IsZero( layout.pos ) )
			helper.UnTransformPos(  layout.pos,  layout.measure, _region.LeftBottom() );

		return true;
	}
	
/*
=================================================
	New
=================================================
*/
	inline WidgetPtr Widget::New (const EngineSubSystemsRef ess)
	{
		return BaseObject::_New( new Widget( EWidget::WIDGET, ess ) );
	}

/*
=================================================
	_SendToAllAttached
=================================================
*/
	inline void Widget::_SendToAllAttached (const WidgetMsg &msg)
	{
		FOR( i, _attachedWidgets ) {
			_attachedWidgets[i]->ProcessMessage( msg );
		}
	}
	
/*
=================================================
	_SendToAttachedWhile
=================================================
*/
	inline bool Widget::_SendToAttachedWhile (const WidgetMsg &msg, bool returnIfResult)
	{
		FOR( i, _attachedWidgets ) {
			if ( _attachedWidgets[i]->ProcessMessage( msg ) == returnIfResult )
				return true;
		}
		return false;
	}
	
/*
=================================================
	_SendToAllAttachedRecursive
=================================================
*/
	inline void Widget::_SendToAllAttachedRecursive (const WidgetMsg &msg)
	{
		typedef WidgetMsg	WM;

		WM	msg2( WM::PROCESS_AND_TRANSMIT, WM::WidgetMessage( msg ) );

		FOR( i, _attachedWidgets ) {
			_attachedWidgets[i]->ProcessMessage( msg2 );
		}
	}
	
/*
=================================================
	_SendToParent
=================================================
*/
	inline void Widget::_SendToParent (const WidgetMsg &msg)
	{
		_parent->ProcessMessage( msg );
	}
	
/*
=================================================
	IsVisible
=================================================
*/
	inline bool Widget::IsVisible () const
	{
		return _visible and _visibleForLayout;
	}
	
/*
=================================================
	SetVisible
=================================================
*/
	inline void Widget::SetVisible (bool visible, bool recursive)
	{
		typedef WidgetMsg	WM;
		ProcessMessage( WM( WM::SET_STATE, WM::SetState( WM::SetState::VISIBLE, visible, recursive ) ) );
	}
	
/*
=================================================
	SetEnabled
=================================================
*/
	inline void Widget::SetEnabled (bool enabled, bool recursive)
	{
		typedef WidgetMsg	WM;
		ProcessMessage( WM( WM::SET_STATE, WM::SetState( WM::SetState::ENABLED, enabled, recursive ) ) );
	}
	
/*
=================================================
	FindWidget
=================================================
*/
	inline bool Widget::FindWidget (uni_c_string name, WidgetPtr &widget)
	{
		CHECK_ERR( not name.Empty() );

		typedef WidgetMsg	WM;

		Widget *	wgt = null;
		bool		res = ProcessMessage( WM( WM::GET_WIDGET, WM::GetWidget( name, wgt ) ) );

		widget = wgt;
		return res;
	}
	
/*
=================================================
	_CheckChangedOnUpdate
=================================================
*/
	inline void Widget::_CheckChangedOnUpdate (const WidgetMsg &msg)
	{
		msg.To< WidgetMsg::Update >().data->widgetsChanged += _stateChanged;

		_stateChanged = false;
	}


}	// UI
}	// Engine